// Copyright 2004 The Apache Software Foundation
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain token copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace AspectSharp.Core.Dispatcher
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.Runtime.Serialization;
    using AspectSharp.Core.Matchers;
    using AspectSharp.Lang.AST;
    using Castle.Core.Interceptor;

    /// <summary>
    /// Summary description for DefaultInvocationDispatcher.
    /// </summary>
    [Serializable]
    public class DefaultAspectInterceptor : IAspectInterceptor, IDeserializationCallback
    {
        private AspectDefinition _aspect;
        private IJoinPointMatcher _matcher;

        [NonSerialized]
        private Dictionary<Type,IInterceptor> _type2AdviceInstance = new Dictionary<Type,IInterceptor>();
        [NonSerialized]
        private Dictionary<MethodInfo, IInterceptor[]> _method2Advices = new Dictionary<MethodInfo, IInterceptor[]>();

        public DefaultAspectInterceptor(AspectDefinition aspect)
        {
            _aspect = aspect;
            _matcher = new DefaultJoinPointMatcher(aspect.PointCuts);
        }

        public void Init(AspectEngine engine)
        {
        }

        public void OnDeserialization(object sender)
        {
            _type2AdviceInstance = new Dictionary<Type,IInterceptor>();
            _method2Advices = new Dictionary<MethodInfo,IInterceptor[]>();
        }

        public void Intercept(IInvocation invocation)
        {
            IInterceptor[] interceptors = GetInterceptors(invocation.Method);

            if (interceptors.Length == 0)
            {
                // Nothing to intercept. 
                // Get on with it!
                invocation.Proceed();
                return;
            }

            foreach (var interceptor in interceptors)
                interceptor.Intercept(invocation);
        }

        private IInterceptor[] GetInterceptors(MethodInfo method)
        {
            IInterceptor[] interceptors = GetFromCache(method);

            if (interceptors == null)
            {
                PointCutDefinition[] pointcuts = _matcher.Match(method);

                if (pointcuts.Length != 0)
                    interceptors = GetInterceptors(pointcuts).ToArray();
                else
                    interceptors = new IInterceptor[0]; 

                RegisterInCache(method, interceptors);
            }

            return interceptors;
        }

        private IInterceptor[] GetFromCache(MethodInfo method)
        {
            IInterceptor[] interceptors = null;
            _method2Advices.TryGetValue(method, out interceptors);
            return interceptors;
        }

        private void RegisterInCache(MethodInfo method, IInterceptor[] interceptors)
        {
            _method2Advices[method] = interceptors;
        }

        private IEnumerable<IInterceptor> GetInterceptors(PointCutDefinition[] pointcuts)
        {
            foreach (var pointcut in pointcuts)
                foreach(var advice in pointcut.Advices)
                    yield return GetInterceptor(advice.TypeReference.ResolvedType);
        }

      

        private IInterceptor GetInterceptor(Type adviceType)
        {
            IInterceptor interceptor = null;

            if (!_type2AdviceInstance.TryGetValue(adviceType,out interceptor))
            {
                interceptor = Activator.CreateInstance(adviceType) as IInterceptor;
                _type2AdviceInstance[adviceType] = interceptor;
            }

            return interceptor;
        }
    }
}
